﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.IO;
using System.Text;

namespace Utilities.Shell
{
    public class Shell
    {
        public Dictionary<string, Action<Scope, string>> CommandHandlers { get; private set; }
        public Scope Scope { get; private set; }
        public bool Running { get; set; }

        private List<string> commandHistory = new List<string>();
        private int currentCommand = 0;

        public Shell()
        {
            Scope = new Scope(this);

            CommandHandlers = new Dictionary<string, Action<Scope, string>>();
            CommandHandlers.Add("clear", ClearCommandHandler);
            CommandHandlers.Add("echo", EchoCommandHandler);
            CommandHandlers.Add("eval", EvalCommandHandler);
            CommandHandlers.Add("exit", ExitCommandHandler);
            CommandHandlers.Add("hex", HexCommandHandler);
            CommandHandlers.Add("if", IfCommandHandler);
            CommandHandlers.Add("null", NullCommandHandler);
            CommandHandlers.Add("pause", PauseCommandHandler);
            CommandHandlers.Add("run", RunCommandHandler);
            CommandHandlers.Add("sleep", SleepCommandHandler);
            CommandHandlers.Add("split", SplitCommandHandler);
            
            Running = false;
        }

        public void Start()
        {
            Running = true;

            string command = "";
            int position = 0;

            bool needRefresh = true;

            Console.TreatControlCAsInput = true;
            Console.CancelKeyPress += CancelKeyPress;

            while (Thread.CurrentThread.ThreadState == ThreadState.Running && !Scope.Exited)
            {
                Log.Lock();

                if (Console.CursorLeft != 2 + position)
                    needRefresh = true;

                if (needRefresh)
                {
                    needRefresh = false;
                    ClearLine();
                    Console.Write("> " + command);
                    if (position < command.Length)
                        Console.Write("\r> " + command.Remove(position));
                }
                if (Console.KeyAvailable)
                {
                    ConsoleKeyInfo keyInfo = Console.ReadKey(true);
                    ConsoleKey consoleKey = keyInfo.Key;
                    char keyChar = keyInfo.KeyChar;

                    switch (consoleKey)
                    {
                        case ConsoleKey.Escape:
                            break;

                        case ConsoleKey.Enter:
                            if (command.Trim() != "")
                            {
                                Console.WriteLine();
                                //mainScope.RunCommand(command);
                                Run(command);

                                commandHistory.Add(command);
                                commandHistory.Reverse();
                                commandHistory = commandHistory.Distinct().ToList();
                                commandHistory.Reverse();
                                currentCommand = commandHistory.Count;
                            }
                            command = "";
                            position = 0;
                            break;

                        #region Position control

                        case ConsoleKey.LeftArrow:
                            if (position > 0)
                                position--;
                            break;

                        case ConsoleKey.RightArrow:
                            if (position < command.Length)
                                position++;
                            break;

                        case ConsoleKey.Home:
                            position = 0;
                            break;

                        case ConsoleKey.End:
                            position = command.Length;
                            break;

                        #endregion
                        #region History control

                        case ConsoleKey.UpArrow:
                            if (currentCommand > 0)
                                currentCommand--;
                            if (currentCommand < commandHistory.Count)
                                command = commandHistory[currentCommand];
                            else
                                command = "";
                            position = command.Length;
                            break;

                        case ConsoleKey.DownArrow:
                            if (currentCommand < commandHistory.Count)
                                currentCommand++;
                            if (currentCommand < commandHistory.Count)
                                command = commandHistory[currentCommand];
                            else
                                command = "";
                            position = command.Length;
                            break;

                        #endregion
                        #region Keys control

                        case ConsoleKey.Backspace:
                            if (position > 0)
                            {
                                if (position < command.Length)
                                    command = command.Remove(position - 1) + command.Substring(position);
                                else
                                    command = command.Remove(command.Length - 1);
                                position--;
                            }
                            break;

                        case ConsoleKey.Delete:
                            if (position < command.Length)
                                command = command.Remove(position) + command.Substring(position + 1);
                            break;

                        default:
                            if (!char.IsControl(keyChar))
                            {
                                if (position < command.Length)
                                    command = command.Remove(position) + keyChar + command.Substring(position);
                                else
                                    command += keyChar;

                                position++;
                            }
                            break;

                        #endregion
                    }

                    needRefresh = true;
                }

                Log.Unlock();
                Thread.Sleep(10);
            }
        }
        public void Stop()
        {
            Running = false;
        }
        public void Run(string command)
        {
            Scope.Add(command);
            Scope.Run("");
        }

        private void CancelKeyPress(object sender, ConsoleCancelEventArgs e)
        {
            e.Cancel = false;
            // TODO: Terminate current instruction
        }

        private void ClearCommandHandler(Scope scope, string parameter)
        {
            Console.Clear();
        }
        private void EchoCommandHandler(Scope scope, string parameter)
        {
            Console.WriteLine(parameter);
        }
        private void EvalCommandHandler(Scope scope, string parameter)
        {
            Console.WriteLine(parameter);
        }
        private void ExitCommandHandler(Scope scope, string parameter)
        {
            int exitLevel = 0;
            if (int.TryParse(parameter, out exitLevel))
            {
                scope.Exit();
                while (--exitLevel > 0 && scope.Parent != null)
                {
                    scope = scope.Parent;
                    scope.Exit();
                }
            }
            else
                scope.Exit();
        }
        private void HexCommandHandler(Scope scope, string parameter)
        {
            byte[] bytes = Encoding.UTF8.GetBytes(parameter);
            Console.WriteLine(bytes.ToHexString(' '));
        }
        private void IfCommandHandler(Scope scope, string parameter)
        {
            string[] args = parameter.SplitWords();
            if (args.Length < 2)
                return;

            if (args[0] == args[1])
                Console.WriteLine("1");
        }
        private void NullCommandHandler(Scope scope, string parameter)
        {
        }
        private void PauseCommandHandler(Scope scope, string parameter)
        {
            Console.Write("Appuyez sur une touche pour continuer ...");
            Console.ReadKey(true);
            Console.WriteLine();
        }
        private void RunCommandHandler(Scope scope, string parameter)
        {
            if (!File.Exists(parameter))
            {
                Console.WriteLine("Script " + '"' + parameter + '"' + " not found");
                return;
            }

            string[] script = File.ReadAllLines(parameter);

            Scope scriptScope = new Scope(this);
            foreach (string line in script)
                if (line.Trim() != "")
                    scriptScope.Add(line);
            scriptScope.Run("");
        }
        private void SleepCommandHandler(Scope scope, string parameter)
        {
            int ms;
            if (!int.TryParse(parameter, out ms))
                return;
            Thread.Sleep(ms);
        }
        private void SplitCommandHandler(Scope scope, string parameter)
        {
            foreach (string word in parameter.SplitWords())
                Console.WriteLine(word);
        }

        public static void ClearLine()
        {
            Console.Write('\r');
            Console.Write(new string(' ', Console.WindowWidth - 1));
            Console.Write('\r');
        }
        public static string GetResult(Action<string> action, string parameter)
        {
            StringWriter tempOut = new StringWriter();
            TextWriter conOut = Console.Out;

            Console.SetOut(tempOut);
            action(parameter);
            Console.SetOut(conOut);

            return tempOut.ToString();
        }
        public static string GetResult(Action<Scope, string> action, Scope scope, string parameter)
        {
            StringWriter tempOut = new StringWriter();
            TextWriter conOut = Console.Out;

            Console.SetOut(tempOut);
            action(scope, parameter);
            Console.SetOut(conOut);

            return tempOut.ToString();
        }
    }
}
